DataObjects.Net is built to simplify writing business code – a code written on high-level language (e.g. C#) and operating on application server.
This brings some new effects in comparison to the case developers faced earlier, when similar code was written in stored procedures:
DataObjects.Net addresses both of these issues:
Transparent persistence and transactional transparency ensure you’re working with entities as if they’re “directly connected” to the underlying database objects:
So DataObjects.Net provides “full WYSIWYG” here.
Generalized batching, future queries and prefetch API allow you to dramatically reduce the chattiness with almost zero efforts.
Note
Transparent persistence and transactional transparency allows developer to be sure that he always deal with actual state: the state he sees is absolutely the same as direct SQL queries performed on the same connection & transaction would show.
For example, if you’re fetching some entity in transaction TA and trying to get its property value in transaction TB, by default its value would be re-fetched there. “By default” indicates there are ways to make DataObjects.Net behave differently, but you must do this explicitly (e.g. you can use DisconnectedState or global cache – they’ll be described further).
Note
As a consequence, DataObjects.Net requires any data access operations to be executed inside logical transactions. “Logical” here means that these transactions define isolation boundaries just for DataObjects.Net. They can be bound to “physical” transactions executed on RDBMS level, or can not – e.g. logical transactions aren’t bound to physical ones when DisconnectedState is attached to Session.
To open a transaction manually, you should call static session.OpenTransaction(...) method (it has several overloads):
using (var session = domain.OpenSession()) {
using (var transactionScope = session.OpenTransaction()) {
var person = new Person();
person.Name = "Barack Obama";
transactionScope.Complete();
}
}
The simplest form of this method opens a transaction in active session and returns its transaction scope – an IDisposable object used to committing or rolling back the transaction.
To commit the transaction, call transactionScope.Complete() method and dispose the transaction scope.
To rollback the transaction, just dispose the transaction scope without calling Complete() method.
Note
In example above we open a transaction inside using block, so transaction scope returned by Open() method will be definitely disposed on leaving this block. If code inside using block throws an exception, Complete() method won’t be invoked. So our transaction will be committed only if code inside it is executed successfully.
If session.OpenTransaction(...) method is invoked, but active session already has associated transaction, it does nothing and return so-called void scope. Such a scope does nothing on Complete() method call and its disposal as well.
Note
It’s possible to open a nested transaction – e.g. by invoking session.OpenTransaction(TransactionOpenMode.New); such a call never returns a void scope. You’ll find the information on nested transactions further.
You can also open a transaction with specified isolation level: session.OpenTransaction(IsolationLevel isolationLevel).
Another way to ensure a particular operation sequence is performed inside a transaction is to use automatic transactions. Automatic transactions are actually provided by TransactionalAspect (PostSharp aspect) looking up for [Transactional] attribute, that can be applied to any instance method, property accessor or constructor of SessionBound descendants (e.g. entities, structures, entity sets or services).
When [Transactional] attribute is applied to a particular method, all operations inside it are wrapped into a transaction:
[Transactional]
public void DoSomething()
{
// ...
}
When this method is compiled, PostSharp rewrites its body to nearly the following one:
public void DoSomething()
{
using (var transactionScope = Session.OpenTransaction()) {
// ...
transactionScope.Complete();
}
}
**Note**
By default DataObjects.Net applies ``[Transactional]`` to all public
instance methods, property accessors and constructors of
``SessionBound`` descendants. To suppress such behavior on a
particular method, apply ``Transactional(false)]`` to it. So you
might notice the default behavior of ``[Transactional]`` attribute
is almost the same as for ``[ActivateSession]`` attribute.
DataObjects.Net fully supports nested transactions, both manual and automatic:
Nesting level is limited only by underlying RDBMS.
// Building the domain
var domain = BuildDomain();
using (var session = domain.OpenSession()) {
using (var transactionScope = session.OpenTransaction()) {
// Creating user
var dmitri = new User {
Name = "Dmitri"
};
// Modifying the entity
dmitri.Name = "Dmitri Maximov";
// Opening new nested transaction
using (var nestedScope = session.OpenTransaction(TransactionOpenMode.New)) {
// Removing the entity
dmitri.Remove();
Assert.IsTrue(dmitri.IsRemoved);
AssertEx.Throws<InvalidOperationException>(() => {
var dmitryName = dmitri.Name;
});
// No nestedScope.Complete(), so nested transaction will be rolled back
}
// Transparent Entity state update
Assert.IsFalse(dmitri.IsRemoved);
Assert.AreEqual("Dmitri Maximov", dmitri.Name);
// Repeating the same using transactional method
AssertEx.Throws<InvalidOperationException>(() => {
dmitri.RemoveAndCancel();
});
// Transparent Entity state update
Assert.IsFalse(dmitri.IsRemoved);
Assert.AreEqual("Dmitri Maximov", dmitri.Name);
// Marking the transaction scope as completed to commit it
transactionScope.Complete();
}
}
[Transactional(TransactionOpenMode.New)]
public void RemoveAndCancel()
{
Remove();
throw new InvalidOperationException("Cancelled.");
}